package main

import "fmt"

/**
特点：
无需声明原型
支持不定 变参
支持多返回值
支持命令返回参数
支持匿名函数和闭包
函数也是一种类型，一个喊出可以复制给变量。

不支持 嵌套 一个包里不能有两个名字一样的函数
不支持重载
不支持 默认参数
*/

/**
函数声明：

函数定义格式：
func name ([parma list]) [return_types]{
	函数体
}
*/

// 一个返回值不需要加括号
func test(x int) int {
	return x + 1
}

// 多个返回值需要加括号
func test2(x, y int) (int, int) {
	return x + 1, y + 1
}

/*
*
函数参数
函数的参数传递方式有两种，一种是值传递，一种是引用传递
值传递：
值传递就是在调用函数的时候将实际的参数赋值一份到传递函数中，这样在函数中对参数进行修改不会影响到是将的参数
例子：test(a,b)
引用传递：
引用传递就是指在调用函数的时候将实际的位置插队到函数中，那么在尚总对参数所进行的修改，将影响到是将参数。
例子 test(&a,&b)

无论是值传递还是引用传递。传递给函数的都是变量的副本，不过值传递是值的拷贝，引用传递则是地址的拷贝，一般来说地址拷贝更为高效
值靠背取决拷贝对象的大小，对象越大性能越低。
*/
func testParam1(a, b int) {
	a += 1
	b += 1
}
func testParam2(a, b *int) {
	*a += 1
	*b += 1
}

/*
*
不定参数传递
func test(args ...int){}   //0个或者多个参数
*/
func manyParams(parmas ...int) int {
	result := 0
	for _, v := range parmas {
		result += v
	}
	return result
}

/**
函数返回值
函数返回值的忽略 接收函数返回值时，可以使用_忽略不需要的返回值

有返回值的函数可以直接作为其他函数的入参
*/

/**
函数的命名返回值
函数的返回值可以被命名，就像在函数体开头声明变量
*/

func namedReturn(a, b int) (result int) {
	result = a + b
	return
}

//命名返回参数可被同名局部变量遮蔽，此时需要显式返回 (现在好像不允许定义同名的了)
//直接返回语句，不指定变量名的应该使用在短函数中长函数中容易出错。

// 命令返回参数允许 defer 延迟调用通过闭包 读取和修改。
// 显示返回之前会先修改命名返回参数的值
func testDefer(a, b int) (result int) {
	defer func() {
		result += 10
	}()
	return a + b
}

/**
理解Golang 的延迟调用defer
特征
关键字 defer 用于注册延迟调用
这些调调用直到return 跳转前才被执行。因此，可以用来做资源清理
多个 defer 语句按照先进后出的方式执行。
defer 语句中的变量，在defer 声明式就决定了

用途
关闭文件句柄
锁资源释放
数据库连接释放

Go 语言中的 defer 语句用于延迟函数的调用，每次defer 都会把一个函数压入栈中，函数返回前在把延迟的函数去除并执行。
Go 中的 defer 可以帮助我们处理容易忽略的问题，如资源释放，连接关闭等。

1.延迟函数的参数 在defer 语句出现的时候就已经确定下来了。
实例：
func a(){
	i:=0
	defer fmt.Println(i)
	i++
	return
}
这个函数执行的输出结果是0，而不是1

注意： 对于指针类型的参数 规则仍然适用，只不过延迟函数的参数是一个地址，这种情况下，defer 后面的2语句对变量的修改可能会影响延迟函数

2. 延迟函数按照先进后出的顺序执行，即先出现的 defer 最后执行。

3. 延迟函数可能操作主函数的具名返回值（命名返回值）
定义defer 函数，即主函数可能有返回值，返回值有没有名字没关系，defer 所作用的函数，即延迟函数可能会影响到返回值。
若要理解延迟函数是如何影响主函数的返回值的，只要明白函数是如何返回的就足够了。
3.1函数返回过程（命名返回值的情况）
有一个事实不需要了解一下，关键词return 不是一个原子操作，实际上 return 值代理汇编指令 ret ,即将跳转程序执行，
比如return i ,实际上要分两步进行，即，将i 值存入栈中作为返回值，然后执行跳转，而 defer 的执行时机正是跳转前，
所以说defer 所以说defer 执行前还是有机会操作返回值的。

3.2主函数拥有匿名返回值，返回字面值
一个主函数有用一个匿名的返回值，返回值使用字面值，比如返回 1,2，“hello”
这样的值，这种情况下 defer 语句是无法操作返回值的。

3.3 主函数有用匿名返回值，返回变量
一个主函数有用一个匿名返回值，返回使用本地或者全局变量，这种情况下，defer 语句可以引用到返回值，
但不会改变返回值。一个返回本地变量的函数
func foo() int {
	var i int
	defer func(){
		i++
	}()
	return i
}

3.4 主函数拥有具名返回值
主函数声明语句中带名字的防护机制，挥别初始化成一个局部变量，函数内部可以像使用局部变量一样使用该返回值。
如果defer 语句操作该返回值，可能会改变返回结果。
func foo() (ret int) {
	defer func() {
	    ret++
	}()
	return 0
}

*/

/**
匿名函数
匿名函数是指不需要定义函数名的一种函数实现方式。
在Go里面函数可以像普通变量一样被传递或使用，GO 支持随时在代码中定义匿名函数。
匿名函数由一个不带函数名的函数和函数体组成。匿名函数的优越性在于可以直接使用函数内的变量，不用重新定义
func main() {
    getSqrt := func(a float64) float64 {
        return math.Sqrt(a)
    }
    fmt.Println(getSqrt(4))
}
*/

func main() {
	fmt.Println("Hello, world!")
	//调用函数
	//fmt.Println(test(1))
	//fmt.Println(test2(1, 2))
	//a := test(1)
	//fmt.Println(a)
	//b, c := test2(1, 2)
	//fmt.Println(b, c)
	//函数传参
	//param1 := 1
	//param2 := 2
	//fmt.Println("原始值：", param1, param2)
	//testParam1(param1, param2)
	//fmt.Println("值传递", param1, param2)
	//testParam2(&param1, &param2)
	//fmt.Println("引用传递", param1, param2)

	//多个参数
	//fmt.Println(manyParams(1, 2, 3))

	//fmt.Println(testAdd(test4()))

	//命令返回参数使用 defer 延迟调用
	fmt.Println(testDefer(1, 2))
}

func test4() int {
	return 1
}
func testAdd(n ...int) int {
	result := 0
	for _, v := range n {
		result += v
	}
	return result
}
